/* ===================

AX2E (AXESCON XACML 2.0 Engine) is the Java authorization engine, which implements OASIS XACML 2.0 standard.
Copyright (C) 2007 AXESCON LLC

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA


Contact: AXESCON LLC - info{at}axescon.com

 =================== */
package axescon.xacml.xb.ctx;

import axescon.xacml.api.PolicyFactory;
import axescon.xacml.api.XacmlException;
import axescon.xacml.model.policy.*;
import axescon.xacml.model.policy.impl.*;
import axescon.xacml.model.shared.Decision;
import org.apache.xmlbeans.QNameSetBuilder;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlException;
import x0PolicySchemaOs.oasisNamesTcXacml2.*;

import javax.xml.namespace.QName;
import java.io.Reader;
import java.io.IOException;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Logger;


/**
 * @author argyn
 *         Date: Sep 19, 2005
 *         Responsibilities: load RequestFactory from XML and save it to XML
 *         using XMLBeans
 */
public class XBPolicyFactory implements PolicyFactory {
    private XBObligationFactory xbObligationFactory = new XBObligationFactory();
    private XBAttributeDesignatorFactory xbAttributeDesignatorFactory = new XBAttributeDesignatorFactory();
    private XBAttributeSelectorFactory xbAttributeSelectorFactory = new XBAttributeSelectorFactory();

    Logger log = Logger.getLogger(getClass().getName());

    public XBPolicyTargetFactory getXbPolicyTargetFactory() {
        return xbPolicyTargetFactory;
    }

    public void setXbPolicyTargetFactory(XBPolicyTargetFactory xBPolicyTargetFactory) {
        this.xbPolicyTargetFactory = xBPolicyTargetFactory;
    }


    public XBAttributeDesignatorFactory getXbAttributeDesignatorFactory() {
        return xbAttributeDesignatorFactory;
    }

    public void setXbAttributeDesignatorFactory(XBAttributeDesignatorFactory xbAttributeDesignatorFactory) {
        this.xbAttributeDesignatorFactory = xbAttributeDesignatorFactory;
    }


    public XBAttrValueFactoryManager getXbAttrValueFactoryManager() {
        return xbAttrValueFactoryManager;
    }

    public void setXbAttrValueFactoryManager(XBAttrValueFactoryManager xbAttrValueFactoryManager) {
        this.xbAttrValueFactoryManager = xbAttrValueFactoryManager;
    }

    public XBObligationFactory getXbObligationFactory() {
        return xbObligationFactory;
    }

    public void setXbObligationFactory(XBObligationFactory xbObligationFactory) {
        this.xbObligationFactory = xbObligationFactory;
    }

    public XBAttributeSelectorFactory getXbAttributeSelectorFactory() {
        return xbAttributeSelectorFactory;
    }

    public void setXbAttributeSelectorFactory(XBAttributeSelectorFactory xbAttributeSelectorFactory) {
        this.xbAttributeSelectorFactory = xbAttributeSelectorFactory;
    }

    public PolicyObject getPolicyObject(Reader r) throws XacmlException {


        XmlObject xdoc = null;
        try {
            xdoc = XmlObject.Factory.parse(r);
        } catch (Exception e) {
            log.throwing("XBPolicyFactory","getPolicyObject(Reader r)",e);
            throw new XacmlException("can't parse policy file",e);
        }


        if (xdoc instanceof PolicyDocument) {
            return getPolicy(((PolicyDocument) xdoc.changeType(PolicyDocument.type)).getPolicy(), null);
        } else if (xdoc instanceof PolicySetDocument) {
            return getPolicySet(((PolicySetDocument) xdoc.changeType(PolicySetDocument.type)).getPolicySet(), null);
        } else
            throw new IllegalArgumentException();

    }


    /**
     * @param xbpt
     * @param parent null indicates, that this policy is the root.
     * @return
     * @throws XacmlException
     */
    private Policy getPolicy(PolicyType xbpt, PolicySet parent) throws XacmlException {

        PolicyImpl ret = new PolicyImpl();
        if (parent == null) {
            XmlCursor cur = xbpt.newCursor();
            Map map = new TreeMap();
            cur.getAllNamespaces(map);
            cur.dispose();
            ret.setNamespacesDecls(map);
        }
        ret.setParentPolicyObject(parent);

// common props
        ret.setDescription(xbpt.getDescription());
        ret.setId(xbpt.getPolicyId());
        ret.setVersion(xbpt.getVersion());
        ret.setDefaults(getDefaults(xbpt.getPolicyDefaults(), ret));

        try {
            ret.setTarget(getXbPolicyTargetFactory().getTarget(xbpt.getTarget(), ret));
            ret.setCombinerParametersList(getCombinerParametersArray(xbpt.getCombinerParametersArray(), ret));
            if (xbpt.isSetObligations())
                ret.setObligationArray(getXbObligationFactory().getObligationArray(xbpt.getObligations().getObligationArray(), ret));

            ret.setVariableDefinitionArray(getVariableDefinitionArray(xbpt.getVariableDefinitionArray(), ret));

// specific props
            ret.setRuleCombiningAlgId(xbpt.getRuleCombiningAlgId());
            ret.setRuleArray(getRuleArray(xbpt, ret));
            ret.setRuleCombinerParametersArray(getRuleCombinerParametersArray(xbpt.getRuleCombinerParametersArray(), ret));
        } catch (Exception e) {
            log.severe("can't parse policy file, one of the elements is incorrect");
            log.throwing("XBPolicyFactory","getPolicy(PolicyType xbpt, PolicySet parent)",e);
            throw new XacmlException("can't parse policy file",e);
        }

        return ret;
    }


    private PolicySet getPolicySet(PolicySetType xbpt, PolicySet parent) throws XacmlException {

        PolicySetImpl ret = new PolicySetImpl();
        if (parent == null) {
            ret.setRootPolicyObject(ret);
            XmlCursor cur = xbpt.newCursor();
            Map map = new TreeMap();
            cur.getAllNamespaces(map);
            cur.dispose();
            ret.setNamespacesDecls(map);
        }
        ret.setParentPolicyObject(parent);

// common props
        ret.setDescription(xbpt.getDescription());
        ret.setId(xbpt.getPolicySetId());
        ret.setVersion(xbpt.getVersion());
        ret.setDefaults(getDefaults(xbpt.getPolicySetDefaults(), ret));

        try {
            ret.setTarget(getXbPolicyTargetFactory().getTarget(xbpt.getTarget(), ret));
            ret.setCombinerParametersList(getCombinerParametersArray(xbpt.getCombinerParametersArray(), ret));
            if (xbpt.isSetObligations())
                ret.setObligationArray(getXbObligationFactory().getObligationArray(xbpt.getObligations().getObligationArray(), ret));
//        ret.setVariableDefinitionList(xbpt.get);

// specific props
            ret.setPolicyCombiningAlgId(xbpt.getPolicyCombiningAlgId());
            ret.setPolicyCombinerParametersArray(getPolicyCombinerParametersArray(xbpt.getPolicyCombinerParametersArray(), ret));
            ret.setPolicySetCombinerParametersArray(getPolicySetCombinerParametersArray(xbpt.getPolicySetCombinerParametersArray(), ret));
        } catch (Exception e) {
            log.severe("can't parse policy set file, one of the elements is incorrect");
            log.throwing("XBPolicyFactory","getPolicySet(PolicySetType xbpt, PolicySet parent)",e);
            throw new XacmlException("can't parse policy set file",e);
        }


        String uri = "urn:oasis:names:tc:xacml:2.0:policy:schema:os";
        QNameSetBuilder qnsb = new QNameSetBuilder();
        qnsb.add(new QName(uri, "Policy"));
        qnsb.add(new QName(uri, "PolicySet"));
        qnsb.add(new QName(uri, "PolicySetIdReference"));
        qnsb.add(new QName(uri, "PolicyIdReference"));

        XmlObject xobjs[] = null;
        xobjs = xbpt.selectChildren(qnsb.toQNameSet());

        ret.setPolicyProxyArray(getPolicyProxyArray(xobjs, ret));

        return ret;
    }

    private PolicyProxy[] getPolicyProxyArray(XmlObject xobjs[], PolicySet parent) throws XacmlException {

        PolicyProxy[] ret = new PolicyProxy[xobjs.length];
        for (int i = 0; i < xobjs.length; i++) {
            XmlObject xobj = xobjs[i];
            ret[i] = getPolicyProxy(xobj, parent);
        }
        return ret;
    }

    private PolicyProxy getPolicyProxy(XmlObject xobj, PolicySet parent) throws XacmlException {

        if (xobj instanceof PolicyType) {
            return getPolicy(((PolicyType) xobj.changeType(PolicyType.type)), parent);
        } else if (xobj instanceof PolicySetType) {
            return getPolicySet(((PolicySetType) xobj.changeType(PolicySetType.type)), parent);
        } else if (xobj instanceof IdReferenceType) {
            IdReferenceType idRef = ((IdReferenceType) xobj.changeType(IdReferenceType.type));
            return getIdReference(idRef, parent);
        } else
            throw new IllegalArgumentException();
    }


    private Defaults getDefaults(DefaultsType xbdt, PolicyObject parent)  {
        if (xbdt == null) return null;
        Defaults ret = new DefaultsImpl(xbdt.getXPathVersion());
        ret.setParentPolicyObject(parent);
        return ret;
    }

    private XBPolicyTargetFactory xbPolicyTargetFactory = new XBPolicyTargetFactory();
    private XBAttrValueFactoryManager xbAttrValueFactoryManager = new XBAttrValueFactoryManager();

    private Rule[] getRuleArray(PolicyType xbpt, Policy parent) throws Exception {
        if (xbpt.sizeOfRuleArray() == 0) return null;

        Rule[] ret = new Rule[xbpt.sizeOfRuleArray()];
        for (int i = 0; i < ret.length; i++) {
            ret[i] = getRule(xbpt.getRuleArray(i), parent);
        }
        return ret;
    }


    public Rule getRule(RuleType xbrt, Policy parent) throws XacmlException {
        if (xbrt == null) return null;

        Rule ret = new RuleImpl();
        try {
            if (xbrt.isSetTarget())
                ret.setTarget(getXbPolicyTargetFactory().getTarget(xbrt.getTarget(), parent));
        } catch (Exception e) {
            log.severe("can't parse policy file, rule element is incorrect");
            log.throwing("XBPolicyFactory","getRule(RuleType xbrt, Policy parent)",e);
            throw new XacmlException("can't parse policy file",e);
        }
        ret.setRuleId(xbrt.getRuleId());
        ret.setDescription(xbrt.getDescription());

        ret.setEffect(Decision.forString(xbrt.getEffect().toString()));
        ret.setCondition(getCondition(xbrt.getCondition(), parent));
        ret.setParentPolicyObject(parent);

        return ret;
    }

    public Condition getCondition(ConditionType xbct, Policy parent) throws XacmlException {
        if (xbct == null) return null;


        ExpressionType xbxt = xbct.getExpression();


        Condition ret = new ConditionImpl();


        ret.setExpression(getExpression(xbxt, parent));
        ret.setParentPolicyObject(parent);

        return ret;
    }

    public Expression getExpression(ExpressionType xbet, PolicyObject parent) throws XacmlException {
        if (xbet == null) return null;

        Expression ret = null;
        if (xbet instanceof ApplyType) {
            ret = getApply(xbet, parent);
        } else if (xbet instanceof FunctionType) {
            ret = getFunction(xbet, (Policy) parent);
        } else if (xbet instanceof VariableReferenceType) {
            ret = getVariableReference((VariableReferenceType) xbet, (Policy) parent);
        } else if (xbet instanceof AttributeDesignatorType) {
            try {
                ret = getXbAttributeDesignatorFactory().getAttributeDesignator((AttributeDesignatorType) xbet, parent);
            } catch (Exception e) {
                log.severe("can't parse policy file, AttributeDesignator element is incorrect");
                log.throwing("XBPolicyFactory","getExpression(ExpressionType xbet, PolicyObject parent)",e);
                throw new XacmlException("can't parse policy file",e);
            }
        } else if (xbet instanceof AttributeSelectorType) {
            try {
                ret = getXbAttributeSelectorFactory().getAttributeSelector((AttributeSelectorType) xbet, parent);
            } catch (Exception e) {
                log.severe("can't parse policy file, AttributeSelector element is incorrect");
                log.throwing("XBPolicyFactory","getExpression(ExpressionType xbet, PolicyObject parent)",e);
                throw new XacmlException("can't parse policy file",e);
            }
        } else if (xbet instanceof AttributeValueType) {
            AttributeValueType avt = (AttributeValueType) xbet;

            ret = getXbAttrValueFactoryManager().getAttributeValue(avt, parent);
        } else {

            throw new XacmlException(xbet.xmlText(), XacmlException.ERR_CODE_SYNTAX_UNSUPPORTED_POLICY_ELEMENT);
//            ret = new ExpressionImpl();
        }


        return ret;
    }

    public Apply getApply(ExpressionType xbet, PolicyObject parent) throws XacmlException {
        if (xbet == null) return null;
        ApplyType at = (ApplyType) xbet;
        ApplyImpl ret = new ApplyImpl();
        ret.setFunctionId(at.getFunctionId());
        ret.setExpressionArray(getExpressionArray(at.getExpressionArray(), parent));

        return ret;
    }


    public Function getFunction(ExpressionType xbet, Policy parent) throws XacmlException {
        if (xbet == null) return null;
        FunctionType at = (FunctionType) xbet;
        FunctionImpl ret = new FunctionImpl();
        ret.setFunctionId(at.getFunctionId());
        ret.setParentPolicyObject(parent);
        return ret;
    }


    public VariableReference getVariableReference(VariableReferenceType at, Policy parent) throws XacmlException {
        if (at == null) return null;
        VariableReferenceImpl ret = new VariableReferenceImpl();
        ret.setVariableId(at.getVariableId());
        ret.setParentPolicyObject(parent);
        return ret;
    }

    public VariableDefinition getVariableDefinition(VariableDefinitionType at, Policy parent) throws XacmlException {
        if (at == null) return null;
        VariableDefinitionImpl ret = new VariableDefinitionImpl(at.getVariableId());
        ret.setExpression(getExpression(at.getExpression(), parent));
        ret.setParentPolicyObject(parent);
        return ret;
    }

    /**
     * @param xbxtl  List<ExpressionType>
     * @param parent
     * @return
     * @throws XacmlException
     */
    private Expression[] getExpressionArray(ExpressionType[] xbxtl, PolicyObject parent) throws XacmlException {
        if (xbxtl == null || xbxtl.length == 0) return null;

        Expression[] ret = new Expression[xbxtl.length];
        for (int i = 0; i < ret.length; i++) {
            ret[i] = getExpression(xbxtl[i], parent);
        }
        return ret;
    }


    public IdReference getIdReference(IdReferenceType idRef, PolicyObject parent) throws XacmlException {
        if (idRef == null)
            throw new IllegalArgumentException();

        IdReferenceImpl ret = new IdReferenceImpl();
        ret.setId(idRef.getStringValue());
        ret.setEarliestVersion(idRef.getEarliestVersion());
        ret.setLatestVersion(idRef.getLatestVersion());
        ret.setVersion(idRef.getVersion());
        ret.setParentPolicyObject(parent);

        String elName = idRef.newCursor().getName().getLocalPart();

        if ("PolicySetIdReference".equals(elName)) {
            ret.setPolicySet(true);
        } else if (!"PolicyIdReference".equals(elName))
            throw new IllegalArgumentException();

        return ret;
    }


    private CombinerParameters[] getCombinerParametersArray(CombinerParametersType[] xbotl, PolicyObject parent)
            throws XacmlException {
        if (xbotl == null || xbotl.length == 0) return null;

        CombinerParameters[] ret = new CombinerParameters[xbotl.length];
        for (int i = 0; i < xbotl.length; i++) {
            CombinerParametersType st = xbotl[i];
            ret[i] = getCombinerParameters(st, parent);
        }
        return ret;
    }


    public CombinerParameters getCombinerParameters(CombinerParametersType xbrt, PolicyObject parent)
            throws XacmlException {
        if (xbrt == null) return null;

        CombinerParameters ret = new CombinerParametersImpl();
        ret.setParentPolicyObject(parent);

        /* TODO: finish impl
        ret.setCondition(xbrt.getDescription());
        ret.setEffect(xbrt.getDescription());
        */
        return ret;
    }

    private RuleCombinerParameters[] getRuleCombinerParametersArray(RuleCombinerParametersType[] xbotl,
                                                                    Policy parent) throws XacmlException {
        if (xbotl == null || xbotl.length == 0) return null;

        RuleCombinerParameters[] ret = new RuleCombinerParameters[xbotl.length];

        for (int i = 0; i < xbotl.length; i++) {
            RuleCombinerParametersType st = xbotl[i];
            ret[i] = getRuleCombinerParameters(st, parent);
        }
        return ret;
    }


    public RuleCombinerParameters getRuleCombinerParameters(RuleCombinerParametersType xbrt, Policy parent)
            throws XacmlException {
        if (xbrt == null) return null;

        RuleCombinerParameters ret = new RuleCombinerParametersImpl();
        ret.setParentPolicyObject(parent);

        /* TODO: finish impl
        ret.setCondition(xbrt.getDescription());
        ret.setEffect(xbrt.getDescription());
        */
        return ret;
    }

    /**
     * @param xbotl  List<PolicyCombinerParametersType>
     * @param parent
     * @return List<PolicyCombinerParameters>
     * @throws XacmlException
     */
    private PolicyCombinerParameters[] getPolicyCombinerParametersArray(PolicyCombinerParametersType[] xbotl,
                                                                        PolicyObject parent) throws XacmlException {
        if (xbotl == null || xbotl.length == 0) return null;

        PolicyCombinerParameters[] ret = new PolicyCombinerParameters[xbotl.length];

        for (int i = 0; i < xbotl.length; i++) {
            PolicyCombinerParametersType st = xbotl[i];
            ret[i] = getPolicyCombinerParameters(st, parent);
        }
        return ret;
    }


    public PolicyCombinerParameters getPolicyCombinerParameters(PolicyCombinerParametersType xbrt,
                                                                PolicyObject parent) throws XacmlException {
        if (xbrt == null) return null;

        PolicyCombinerParameters ret = new PolicyCombinerParametersImpl();

        ret.setParentPolicyObject(parent);
        /* TODO: finish impl
        ret.setCondition(xbrt.getDescription());
        ret.setEffect(xbrt.getDescription());
        */
        return ret;
    }

    private PolicySetCombinerParameters[] getPolicySetCombinerParametersArray(
            PolicySetCombinerParametersType[] xbotl, PolicyObject parent) throws XacmlException {
        if (xbotl == null || xbotl.length == 0) return null;

        PolicySetCombinerParameters[] ret = new PolicySetCombinerParameters[xbotl.length];
        for (int i = 0; i < xbotl.length; i++) {
            PolicySetCombinerParametersType st = xbotl[i];
            ret[i] = getPolicySetCombinerParameters(st, parent);
        }
        return ret;
    }


    public PolicySetCombinerParameters getPolicySetCombinerParameters(
            PolicySetCombinerParametersType xbrt, PolicyObject parent) throws XacmlException {
        if (xbrt == null) return null;

        PolicySetCombinerParameters ret = new PolicySetCombinerParametersImpl();
        ret.setParentPolicyObject(parent);

        /* TODO: finish impl
        ret.setCondition(xbrt.getDescription());
        ret.setEffect(xbrt.getDescription());
        */
        return ret;
    }


    public VariableDefinition[] getVariableDefinitionArray(
            VariableDefinitionType[] xbotl, Policy parent) throws XacmlException {
        if (xbotl == null || xbotl.length == 0) return null;

        VariableDefinition[] ret = new VariableDefinition[xbotl.length];
        for (int i = 0; i < xbotl.length; i++) {
            ret[i] = getVariableDefinition(xbotl[i], parent);
        }
        return ret;
    }


}
